/* w_date.c
 *
 * Micropolis, Unix Version.  This game was released for the Unix platform
 * in or about 1990 and has been modified for inclusion in the One Laptop
 * Per Child program.  Copyright (C) 1989 - 2007 Electronic Arts Inc.  If
 * you need assistance with this program, you may contact:
 *   http://wiki.laptop.org/go/Micropolis  or email  micropolis@laptop.org.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.  You should have received a
 * copy of the GNU General Public License along with this program.  If
 * not, see <http://www.gnu.org/licenses/>.
 * 
 *             ADDITIONAL TERMS per GNU GPL Section 7
 * 
 * No trademark or publicity rights are granted.  This license does NOT
 * give you any right, title or interest in the trademark SimCity or any
 * other Electronic Arts trademark.  You may not distribute any
 * modification of this program using the trademark SimCity or claim any
 * affliation or association with Electronic Arts Inc. or its employees.
 * 
 * Any propagation or conveyance of this program must include this
 * copyright notice and these terms.
 * 
 * If you convey this program (or any modifications of it) and assume
 * contractual liability for the program to recipients of it, you agree
 * to indemnify Electronic Arts for any liability that those contractual
 * assumptions impose on Electronic Arts.
 * 
 * You may not misrepresent the origins of this program; modified
 * versions of the program must be marked as such and not identified as
 * the original program.
 * 
 * This disclaimer supplements the one included in the General Public
 * License.  TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS
 * PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY
 * OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK.  THE ENTIRE RISK OF
 * SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU.  ELECTRONIC ARTS
 * DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES,
 * INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY
 * RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING,
 * USAGE, OR TRADE PRACTICE.  ELECTRONIC ARTS DOES NOT WARRANT AGAINST
 * INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL
 * MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE
 * UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE
 * WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE
 * CORRECTED.  NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR
 * ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY.  SOME
 * JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED
 * WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
 * CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY
 * NOT APPLY TO YOU.
 */
#include "sim.h"

short NewDate = 0;
Tcl_HashTable DateCmds;
int DateUpdateTime = 200;

void DoUpdateDate(SimDate * date);
void DoNewDate(SimDate * date);
void DoResizeDate(SimDate * date, int w, int h);
void DestroyDate(SimDate * date);
void InitNewDate(SimDate * date);
int ConfigureSimDate(Tcl_Interp * interp, SimDate * date, int argc, char **argv, int flags);

#define DEF_DATE_FONT	"-Adobe-Helvetica-Bold-R-Normal-*-140-*"
#define DEF_DATE_BG_COLOR	"#b0b0b0"
#define DEF_DATE_BG_MONO	"#ffffff"
#define DEF_DATE_BORDER_WIDTH	"2"
#define DEF_DATE_PADY		"1"
#define DEF_DATE_PADX		"1"
#define DEF_DATE_WIDTH		"0"
#define DEF_DATE_MONTHTAB	"7"
#define DEF_DATE_YEARTAB	"13"

Tk_ConfigSpec DateConfigSpecs[] = {
    {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
     DEF_DATE_FONT, Tk_Offset(SimDate, fontPtr), 0},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
     DEF_DATE_BG_COLOR, Tk_Offset(SimDate, border),
     TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
     DEF_DATE_BG_MONO, Tk_Offset(SimDate, border),
     TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
     DEF_DATE_BORDER_WIDTH, Tk_Offset(SimDate, borderWidth), 0},
    {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
     DEF_DATE_PADX, Tk_Offset(SimDate, padX), 0},
    {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
     DEF_DATE_PADY, Tk_Offset(SimDate, padY), 0},
    {TK_CONFIG_INT, "-width", "width", "Width",
     DEF_DATE_WIDTH, Tk_Offset(SimDate, width), 0},
    {TK_CONFIG_INT, "-monthtab", "monthtab", "MonthTab",
     DEF_DATE_MONTHTAB, Tk_Offset(SimDate, monthTab), 0},
    {TK_CONFIG_INT, "-yeartab", "yeartab", "YearTab",
     DEF_DATE_YEARTAB, Tk_Offset(SimDate, yearTab), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, 0}
};

XDisplay *FindXDisplay();

static void DisplaySimDate(ClientData clientData)
{
    SimDate *date = (SimDate *) clientData;
    Tk_Window tkwin = date->tkwin;

    date->flags &= ~VIEW_REDRAW_PENDING;
//fprintf(stderr, "DisplaySimDate cleared VIEW_REDRAW_PENDING\n");

    assert(date->draw_date_token != 0);
    if(date->draw_date_token != 0)
    {
//    Tk_DeleteTimerHandler(date->draw_date_token);
        date->draw_date_token = 0;
    }

    if(date->visible && (tkwin != NULL) && Tk_IsMapped(tkwin))
    {
        DoUpdateDate(date);
    }
}

void DestroySimDate(ClientData clientData)
{
    SimDate *date = (SimDate *) clientData;

    DestroyDate(date);
}

void EventuallyRedrawDate(SimDate * date)
{
    if(!(date->flags & VIEW_REDRAW_PENDING))
    {
        assert(date->draw_date_token == 0);
        if(date->draw_date_token == 0)
        {
            date->draw_date_token = Tk_CreateTimerHandler(DateUpdateTime, DisplaySimDate, (ClientData) date);
            date->flags |= VIEW_REDRAW_PENDING;
//fprintf(stderr, "EventuallyRedrawDate set VIEW_REDRAW_PENDING\n");
        }
    }
}

void SimDateEventProc(ClientData clientData, XEvent * eventPtr)
{
    SimDate *date = (SimDate *) clientData;

    if((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0))
    {
        date->visible = 1;
        EventuallyRedrawDate(date);
    }
    else if(eventPtr->type == MapNotify)
    {
        date->visible = 1;
    }
    else if(eventPtr->type == UnmapNotify)
    {
        date->visible = 0;
    }
    else if(eventPtr->type == VisibilityNotify)
    {
        if(eventPtr->xvisibility.state == VisibilityFullyObscured)
            date->visible = 0;
        else
            date->visible = 1;
    }
    else if(eventPtr->type == ConfigureNotify)
    {
        DoResizeDate(date, eventPtr->xconfigure.width, eventPtr->xconfigure.height);
        EventuallyRedrawDate(date);
    }
    else if(eventPtr->type == DestroyNotify)
    {
        Tcl_DeleteCommand(date->interp, Tk_PathName(date->tkwin));
        date->tkwin = NULL;
        if(date->flags & VIEW_REDRAW_PENDING)
        {
            assert(date->draw_date_token != 0);
            if(date->draw_date_token != 0)
            {
                Tk_DeleteTimerHandler(date->draw_date_token);
                date->draw_date_token = 0;
            }
            date->flags &= ~VIEW_REDRAW_PENDING;
//fprintf(stderr, "SimDateEventProc cleared VIEW_REDRAW_PENDING\n");
        }
        Tk_EventuallyFree((ClientData) date, DestroySimDate);
    }
}

static void ComputeDateGeometry(SimDate * date)
{
    XCharStruct bbox;
    int dummy;
    unsigned int width, height;
    int charWidth;

    XTextExtents(date->fontPtr, "0", 1, &dummy, &dummy, &dummy, &bbox);
    charWidth = (bbox.lbearing + bbox.rbearing);

    if(date->width == 0)
    {
        char *maxString = "Date:  MMM    1000000";
        int maxStringLength = strlen(maxString);

        XTextExtents(date->fontPtr, maxString, maxStringLength, &dummy, &dummy, &dummy, &bbox);
        width = bbox.lbearing + bbox.rbearing;
    }
    else
    {
        width = date->width * charWidth;
    }

    height = date->fontPtr->ascent + date->fontPtr->descent;

    width += 2 * date->padX;
    height += 2 * date->padY;

    Tk_GeometryRequest(date->tkwin,
                       (int) (width + (2 * date->borderWidth) + 2), (int) (height + (2 * date->borderWidth) + 2));
    Tk_SetInternalBorder(date->tkwin, date->borderWidth);

    date->yearTabX = date->yearTab * charWidth;
    date->monthTabX = date->monthTab * charWidth;
}

int DateCmdconfigure(DATE_ARGS)
{
    if(argc == 2)
    {
        Tk_ConfigureInfo(interp, date->tkwin, DateConfigSpecs, (char *) date, (char *) NULL, 0);
    }
    else if(argc == 3)
    {
        Tk_ConfigureInfo(interp, date->tkwin, DateConfigSpecs, (char *) date, argv[2], 0);
    }
    else
    {
        ConfigureSimDate(interp, date, argc - 2, argv + 2, TK_CONFIG_ARGV_ONLY);
    }
    return TCL_OK;
}

int DateCmdposition(DATE_ARGS)
{
    if((argc != 2) && (argc != 4))
    {
        return TCL_ERROR;
    }
    if(argc == 4)
    {
        if((Tcl_GetInt(interp, argv[2], &date->w_x) != TCL_OK) || (Tcl_GetInt(interp, argv[3], &date->w_y) != TCL_OK))
        {
            return TCL_ERROR;
        }
    }
    sprintf(interp->result, "%d %d", date->w_x, date->w_y);
    return TCL_OK;
}

int DateCmdsize(DATE_ARGS)
{
    if((argc != 2) && (argc != 4))
    {
        return TCL_ERROR;
    }
    if(argc == 4)
    {
        int w, h;

        if(Tcl_GetInt(interp, argv[2], &w) != TCL_OK)
        {
            return TCL_ERROR;
        }
        if(Tcl_GetInt(interp, argv[3], &h) != TCL_OK)
        {
            return TCL_ERROR;
        }
        date->w_width = w;
        date->w_height = h;
    }
    sprintf(interp->result, "%d %d", date->w_width, date->w_height);
    return TCL_OK;
}

int DateCmdVisible(DATE_ARGS)
{
    int visible;

    if((argc != 2) && (argc != 3))
    {
        Tcl_AppendResult(interp, "wrong # args", (char *) NULL);
        return TCL_ERROR;
    }

    if(argc == 3)
    {
        if((Tcl_GetInt(interp, argv[2], &visible) != TCL_OK) || (visible < 0) || (visible > 1))
        {
            Tcl_AppendResult(interp, " bogus args", (char *) NULL);
            return TCL_ERROR;
        }

        date->visible = visible;
    }

    sprintf(interp->result, "%d", date->visible);

    return TCL_OK;
}

int DateCmdReset(DATE_ARGS)
{
    if(argc != 2)
    {
        Tcl_AppendResult(interp, "wrong # args", (char *) NULL);
        return TCL_ERROR;
    }

    date->reset = 1;

//  ComputeDateGeometry(date); // ???

    EventuallyRedrawDate(date);

    return TCL_OK;
}

int DateCmdSet(DATE_ARGS)
{
    if(argc != 4)
    {
        Tcl_AppendResult(interp, "wrong # args", (char *) NULL);
        return TCL_ERROR;
    }

    if((Tcl_GetInt(interp, argv[2], &date->month) != TCL_OK) || (date->month < 0) || (date->month >= 12))
    {
        Tcl_AppendResult(interp, " bogus args", (char *) NULL);
        return TCL_ERROR;
    }

    if((Tcl_GetInt(interp, argv[3], &date->year) != TCL_OK) || (date->year < 0))
    {
        Tcl_AppendResult(interp, " bogus args", (char *) NULL);
        return TCL_ERROR;
    }

//  ComputeDateGeometry(date); // ???

    EventuallyRedrawDate(date);

    return TCL_OK;
}

int DoDateCmd(CLIENT_ARGS)
{
    SimDate *date = (SimDate *) clientData;
    Tcl_HashEntry *ent;
    int result = TCL_OK;
    int (*cmd) ();

    if(argc < 2)
    {
        return TCL_ERROR;
    }

    if((ent = Tcl_FindHashEntry(&DateCmds, argv[1])))
    {
        cmd = (int (*)()) ent->clientData;
        Tk_Preserve((ClientData) date);
        result = cmd(date, interp, argc, argv);
        Tk_Release((ClientData) date);
    }
    else
    {
        Tcl_AppendResult(interp, "unknown command name: \"", argv[0], " ", argv[1], "\".", (char *) NULL);
        result = TCL_ERROR;
    }
    return result;
}

int DateViewCmd(CLIENT_ARGS)
{
    SimDate *date;
    Tk_Window tkwin = (Tk_Window) clientData;

    if(argc < 2)
    {
        Tcl_AppendResult(interp, "wrong # args:  should be \"", argv[0], " pathName ?options?\"", (char *) NULL);
        return TCL_ERROR;
    }

    tkwin = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
    if(tkwin == NULL)
    {
        return TCL_ERROR;
    }

    date = (SimDate *) ckalloc(sizeof(SimDate));

    date->tkwin = tkwin;
    date->interp = interp;
    date->flags = 0;
    date->reset = 1;
    date->month = 0;
    date->year = 0;
    date->lastmonth = 0;
    date->lastyear = 0;

    Tk_SetClass(date->tkwin, "DateView");
    Tk_CreateEventHandler(date->tkwin,
                          VisibilityChangeMask |
                          ExposureMask | StructureNotifyMask, SimDateEventProc, (ClientData) date);
    Tcl_CreateCommand(interp, Tk_PathName(date->tkwin), DoDateCmd, (ClientData) date, (void (*)()) NULL);

/*
  Tk_MakeWindowExist(date->tkwin);
*/

    if(getenv("XSYNCHRONIZE") != NULL)
    {
        XSynchronize(Tk_Display(tkwin), 1);
    }

    InitNewDate(date);
    DoNewDate(date);

    if(ConfigureSimDate(interp, date, argc - 2, argv + 2, 0) != TCL_OK)
    {
        /*
         * XXX: destroy date 
         */
        Tk_DestroyWindow(date->tkwin);
        return TCL_ERROR;
    }

    interp->result = Tk_PathName(date->tkwin);
    return TCL_OK;
}

int ConfigureSimDate(Tcl_Interp * interp, SimDate * date, int argc, char **argv, int flags)
{
    if(Tk_ConfigureWidget(interp, date->tkwin, DateConfigSpecs, argc, argv, (char *) date, flags) != TCL_OK)
    {
        return TCL_ERROR;
    }

    Tk_SetBackgroundFromBorder(date->tkwin, date->border);

    ComputeDateGeometry(date);

    EventuallyRedrawDate(date);

    return TCL_OK;
}

void date_command_init(void)
{
    Tcl_CreateCommand(tk_mainInterp, "dateview", DateViewCmd, (ClientData) MainWindow, (void (*)()) NULL);

    Tcl_InitHashTable(&DateCmds, TCL_STRING_KEYS);

#define DATE_CMD(name) HASHED_CMD(Date, name)

    DATE_CMD(configure);
    DATE_CMD(position);
    DATE_CMD(size);
    DATE_CMD(Visible);
    DATE_CMD(Reset);
    DATE_CMD(Set);
}

void InitNewDate(SimDate * date)
{
    date->next = NULL;

/* This stuff was initialized in our caller (DateCmd) */
/*  date->tkwin = NULL; */
/*  date->interp = NULL; */
/*  date->flags = 0; */

    date->x = NULL;
    date->visible = 0;
    date->w_x = date->w_y = 0;
    date->w_width = date->w_height = 0;
    date->pixmap = None;
    date->pixels = NULL;
    date->fontPtr = NULL;
    date->border = NULL;
    date->borderWidth = 0;
    date->padX = 0;
    date->padY = 0;
    date->width = 0;
    date->monthTab = 0;
    date->monthTabX = 0;
    date->yearTab = 0;
    date->yearTabX = 0;
    date->draw_date_token = 0;
    date->reset = 1;
    date->year = 0;
    date->month = 0;
    date->lastyear = 0;
    date->lastmonth = 0;

    date->x = FindXDisplay(date->tkwin);
    IncRefDisplay(date->x);

    date->pixels = date->x->pixels;
    date->fontPtr = NULL;

    DoResizeDate(date, 16, 16);
}

void DestroyDate(SimDate * date)
{
    SimDate **gp;

    for(gp = &sim->date; (*gp) != NULL; gp = &((*gp)->next))
    {
        if((*gp) == date)
        {
            (*gp) = date->next;
            sim->dates--;
            break;
        }
    }

    if(date->pixmap != None)
    {
        XFreePixmap(date->x->dpy, date->pixmap);
        date->pixmap = None;
    }

    DecRefDisplay(date->x);

    ckfree((char *) date);
}

void DoResizeDate(SimDate * date, int w, int h)
{
    date->w_width = w;
    date->w_height = h;

    if(date->pixmap != None)
    {
        XFreePixmap(date->x->dpy, date->pixmap);
        date->pixmap = None;
    }
    date->pixmap = XCreatePixmap(date->x->dpy, date->x->root, w, h, date->x->depth);
    if(date->pixmap == None)
    {
        fprintf(stderr, "Sorry, Micropolis can't create a pixmap on X display \"%s\".\n", date->x->display);
        sim_exit(1);            // Just sets tkMustExit and ExitReturn
        return;
    }
}

void DoNewDate(SimDate * date)
{
    sim->dates++;
    date->next = sim->date;
    sim->date = date;

    NewDate = 1;
}

#define BORDER 1

void DoUpdateDate(SimDate * date)
{
    Display *dpy;
    GC gc;
    Pixmap pm;
    int *pix;
    int w, h, x, y;

    if(!date->visible)
    {
        return;
    }

    dpy = date->x->dpy;
    gc = date->x->gc;
    pm = date->pixmap;
    pix = date->pixels;

    w = date->w_width;
    h = date->w_height;

    XSetFont(date->x->dpy, date->x->gc, date->fontPtr->fid);

#if 0
    if(date->x->color)
    {
        XSetForeground(dpy, gc, pix[COLOR_LIGHTGRAY]);
    }
    else
    {
        XSetForeground(dpy, gc, pix[COLOR_WHITE]);
    }
#else
    XSetForeground(dpy, gc, Tk_3DBorderColor(date->border)->pixel);
#endif

    XFillRectangle(dpy, pm, gc, 0, 0, w, h);

    if((w -= (2 * BORDER)) < 1)
        w = 1;
    if((h -= (2 * BORDER)) < 1)
        h = 1;

    x = date->borderWidth + date->padX + 1;
    y = date->borderWidth + date->padY + date->fontPtr->ascent;

    if(date->reset)
    {
        date->reset = 0;
        date->lastyear = date->year;
        date->lastmonth = date->month;
    }

    {
        char *dateString = "Date:";
        char yearString[256];
        int month = date->month;
        int year = date->year;
        int lastmonth = date->lastmonth;
        int lastyear = date->lastyear;
        int yearsPassed;
        int monthsPassed;

        yearsPassed = (year - lastyear);
        if(yearsPassed < 0)
            yearsPassed = 1;
        if(yearsPassed > 9)
            yearsPassed = 9;
        monthsPassed = (month - lastmonth) + (12 * yearsPassed);

        if(monthsPassed > 11)
            monthsPassed = 11;
        if(monthsPassed == 1)
            monthsPassed = 0;
        if(monthsPassed)
        {
            int m = lastmonth;
            int i;

            XSetForeground(dpy, gc, pix[COLOR_DARKGRAY]);

            for(i = 0; i < monthsPassed; i++)
            {

                XDrawString(date->x->dpy, pm, date->x->gc,
                            x + date->monthTabX, y, dateStr[m], strlen(dateStr[date->month]));

                m++;
                if(m == 12)
                    m = 0;
            }

            if(year != lastyear)
            {
                int yy = lastyear;

                if((year - yy) > 10)
                {
                    yy = year - 10;
                }

                for(i = yy; i < year; i++)
                {
                    sprintf(yearString, "%d", i);

                    XDrawString(date->x->dpy, pm, date->x->gc, x + date->yearTabX, y, yearString, strlen(yearString));
                }
            }

            EventuallyRedrawDate(date);
        }

        date->lastmonth = month;
        date->lastyear = year;

        XSetForeground(dpy, gc, pix[COLOR_BLACK]);

        XDrawString(date->x->dpy, pm, date->x->gc, x, y, dateString, strlen(dateString));

        XDrawString(date->x->dpy, pm, date->x->gc,
                    x + date->monthTabX, y, dateStr[date->month], strlen(dateStr[date->month]));

        sprintf(yearString, "%d", year);

        XDrawString(date->x->dpy, pm, date->x->gc, x + date->yearTabX, y, yearString, strlen(yearString));
    }

    XCopyArea(date->x->dpy, date->pixmap,
              Tk_WindowId(date->tkwin), date->x->gc, 0, 0, date->w_width, date->w_height, 0, 0);
}
